package cn.ilikes.tools.nosql.jpa.graphs;

import java.util.Collection;
import java.util.Map;

import javax.persistence.CascadeType;

import org.slf4j.Logger;

import cn.ilikes.tools.logs.Loggerfactory;
import cn.ilikes.tools.nosql.jpa.JPASQLPersistenUnitEnv;
import cn.ilikes.tools.nosql.jpa.graphs.Node.NodeLink;
import cn.ilikes.tools.nosql.jpa.graphs.NodeLifeCycle.LifeCycle;
import cn.ilikes.tools.nosql.jpa.metadatas.EntityMetaData;
import cn.ilikes.tools.nosql.jpa.metadatas.IEntityMetaDataFactory;
import cn.ilikes.tools.nosql.jpa.metadatas.elements.EntityFields;
import cn.ilikes.tools.nosql.jpa.metadatas.elements.Relation;
import cn.ilikes.tools.nosql.jpa.metadatas.elements.Relation.ForeignKey;
import cn.ilikes.tools.nosql.jpa.unitls.JPAUtils;
import cn.ilikes.tools.nosql.jpa.unitls.PropertyAccessorHelper;

public class NodeGraphBuilder implements INodeGraphBuilder {

	private Logger logger = Loggerfactory.instance(NodeGraphBuilder.class);

	private JPASQLPersistenUnitEnv hbasePersistenUnitEnv;

	private INodeBuilderListener nodeBuilderListener;

	public NodeGraphBuilder(JPASQLPersistenUnitEnv hbasePersistenUnitEnv, INodeBuilderListener nodeBuilderListener) {

		this.hbasePersistenUnitEnv = hbasePersistenUnitEnv;
		this.nodeBuilderListener = nodeBuilderListener;

	}

	private IEntityMetaDataFactory getEntityMetaDataFactory() {
		return hbasePersistenUnitEnv.getMetaDataManagerFactory();
	}

	@Override
	public void buildNodeStack(Node node, BuildType buildType) {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "start build Node Stack   " + node.getEntity().getClass().getSimpleName());
		switch (buildType) {
		case remove: {
			buildChild(node, buildType);
			buildLocal(node, buildType);
			buildParents(node, buildType);
			break;
		}

		default: {
			buildParents(node, buildType);
			buildLocal(node, buildType);
			buildChild(node, buildType);
			break;
		}
		}

	}

	private void buildParents(Node node, BuildType buildType) {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "build parents  nodes   " + node.getEntity().getClass().getSimpleName());
		EntityMetaData entityMetaData = node.getEntityMetaData();
		Map<String, EntityFields> entityFields = entityMetaData.getEntityFields();
		for (EntityFields entityField : entityFields.values()) {
			if (entityField.getjPAField() == null || !entityField.getjPAField().isRelation() || entityField.getEntityFieldsType().equals(EntityFields.EntityFieldsType.RoreignKey))
				continue;

			Relation relation = entityField.getjPAField().getRelation();
			// hand many to one 
			if (relation.getType().equals(ForeignKey.MANY_TO_ONE)) {
				String JoinColumnName = relation.getJoinColumnName();
				//   @ManyToOne(fetch = FetchType.LAZY)
				//   @JoinColumn(name = "ALBUM_ID")
				if (JoinColumnName == null || JoinColumnName.isEmpty())
					throw new NodeGraphBuilderRunTimeException("not find JoinColumnName   in  " + node.getEntity().getClass().getSimpleName());
				Object object = PropertyAccessorHelper.getObject(node.getEntity(), entityField.getField());
				if (object != null) {
					if (relation.getCascades().contains(CascadeType.MERGE)) {
						EntityMetaData entityMetaData_ = getEntityMetaDataFactory().selectIEntityMetaData(object.getClass());
						//						Node node_ = new Node(entityMetaData_, object);
						Node node_ = Node.createtNode(entityMetaData_, object, LifeCycle.newses);
						buildNodeStack(node_, buildType);
					} else {

						NodeLink nodeLink = node.new NodeLink();
						nodeLink.setSourceObject(node.getEntity());
						nodeLink.setTargetObject(object);
						node.getNodeLink().put(entityField, nodeLink);
					}

				}

			}
			if (relation.getType().equals(ForeignKey.ONE_TO_ONE)) {

				//				String JoinColumnName = relation.getJoinColumnName();
				if (relation.getMappedBy().isEmpty() && !relation.getJoinColumnName().isEmpty()) {
					Object object = PropertyAccessorHelper.getObject(node.getEntity(), entityField.getField());
					if (object != null) {
						{
							NodeLink nodeLink = node.new NodeLink();
							nodeLink.setSourceObject(node.getEntity());
							nodeLink.setTargetObject(object);
							node.getNodeLink().put(entityField, nodeLink);
						}

						if (JPAUtils.containsCascade(relation.getCascades(), CascadeType.ALL)) {
							EntityMetaData entityMetaData_ = getEntityMetaDataFactory().selectIEntityMetaData(object.getClass());
							//							Node node_ = new Node(entityMetaData_, object);
							Node node_ = Node.createtNode(entityMetaData_, object, LifeCycle.newses);
							buildNodeStack(node_, buildType);
						}

					}
				}

			}
		}

	}

	private void buildLocal(Node local, BuildType buildType) {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.info(logger, "build local   nodes   " + local.getEntity().getClass().getSimpleName());
		nodeBuilderListener.buildNode(local);
	}

	private void buildChild(Node child, BuildType buildType) {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.info(logger, "build childs  nodes   " + child.getEntity().getClass().getSimpleName());
		EntityMetaData entityMetaData = child.getEntityMetaData();

		Map<String, EntityFields> entityFields = entityMetaData.getEntityFields();
		for (EntityFields entityField : entityFields.values()) {
			if (entityField.getjPAField() == null || !entityField.getjPAField().isRelation() || entityField.getEntityFieldsType().equals(EntityFields.EntityFieldsType.RoreignKey))
				continue;
			Relation relation = entityField.getjPAField().getRelation();
			if (relation.getType().equals(ForeignKey.ONE_TO_MANY)) {
				Collection<Object> collections = (Collection<Object>) PropertyAccessorHelper.getObject(child.getEntity(), entityField.getField());
				if (collections != null) {
					for (Object collection : collections) {
						if (!relation.getMappedBy().isEmpty()) {//双向
							String mapBy = relation.getMappedBy();
							EntityMetaData entityMetaData_ = getEntityMetaDataFactory().selectIEntityMetaData(collection.getClass());
							EntityFields entityFields_ = entityMetaData_.getEntityFields().get(mapBy);
							if (entityFields_ == null)
								throw new NodeGraphBuilderRunTimeException("not find MappedBy " + mapBy + " in  "
										+ collection.getClass().getSimpleName());
							//							Node node_ = new Node(entityMetaData_, collection);
							Node node_ = Node.createtNode(entityMetaData_, collection, LifeCycle.newses);
							NodeLink nodeLink = node_.new NodeLink();
							nodeLink.setTargetObject(child.getEntity());
							nodeLink.setSourceObject(collection);
							if (Loggerfactory.isdebug(logger))
								Loggerfactory.info(logger, "add   NodeLink   node : " + node_.getEntity().getClass().getSimpleName()
										+ "   entityField : " + entityField.getFieldName() + "   ;  ");
							node_.getNodeLink().put(entityField, nodeLink);
							if (Loggerfactory.isdebug(logger))
								Loggerfactory.info(logger, "hand  OneToMany     MappedBy   " + node_.getEntity().getClass().getSimpleName());
							buildNodeStack(node_, buildType);
						} else {// 单项

							EntityMetaData entityMetaData_ = getEntityMetaDataFactory().selectIEntityMetaData(collection.getClass());
							//							String joinColumnName = relation.getJoinColumnName();
							//检查joinColumnName是否在目标Entity里Column Name
							//在目标Entity里找joinColumnName EntityFields;
							//							EntityFields entityField_ = entityMetaData_.findEntityFieldsByJPAColumn(joinColumnName);
							//							if (entityField_ == null)
							//								throw new JPAHBaseClientsRuntimeException("not find  @joinColumnName  name " + joinColumnName + " in  "
							//										+ collection.getClass().getSimpleName());
							//							Node node_ = new Node(entityMetaData_, collection);
							Node node_ = Node.createtNode(entityMetaData_, collection, LifeCycle.newses);
							NodeLink nodeLink = node_.new NodeLink();
							nodeLink.setSourceObject(collection);
							nodeLink.setTargetObject(child.getEntity());
							node_.getNodeLink().put(JPAUtils.getForeignkeys(collection.getClass()), nodeLink);
							if (Loggerfactory.isdebug(logger))
								Loggerfactory.info(logger, "hand  OneToMany     单向    " + node_.getEntity().getClass().getSimpleName());
							buildNodeStack(node_, buildType);
						}
					}
				}

			}

			if (relation.getType().equals(ForeignKey.ONE_TO_ONE)) {

				if (Loggerfactory.isdebug(logger))
					Loggerfactory.info(logger, "hand  OneToOne     node  " + child.getEntity().getClass().getSimpleName() + " Field  " + entityField.getFieldName()
							+ " => ");
				if (!relation.getMappedBy().isEmpty()) {//双向
					Object object = PropertyAccessorHelper.getObject(child.getEntity(), entityField.getField());
					if (object != null) {
						String mapBy = relation.getMappedBy();
						EntityMetaData entityMetaData_ = getEntityMetaDataFactory().selectIEntityMetaData(object.getClass());
						EntityFields entityFields_ = entityMetaData_.getEntityFields().get(mapBy);
						if (entityFields_ == null)
							throw new NodeGraphBuilderRunTimeException("not find MappedBy " + mapBy + " in  " + object.getClass().getSimpleName());
						Node node_ = Node.createtNode(entityMetaData_, object, LifeCycle.newses);
						NodeLink nodeLink = node_.new NodeLink();
						nodeLink.setSourceObject(object);
						nodeLink.setTargetObject(child.getEntity());
						node_.getNodeLink().put(entityField, nodeLink);
						if (Loggerfactory.isdebug(logger))
							Loggerfactory.info(logger, "hand  OneToOne     MappedBy   " + node_.getEntity().getClass().getSimpleName());
						buildNodeStack(node_, buildType);
					}

				}

			}

		}
	}

}
